package com.zh.webcommon.upload;

import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.errors.MinioException;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.util.Objects;
import java.util.function.BiFunction;

import static com.zh.webcommon.upload.FileKind.AUDIO_FILE;
import static com.zh.webcommon.upload.FileKind.IMAGE_FILE;
import static com.zh.webcommon.upload.FileKind.REGULAR_FILE;
import static com.zh.webcommon.upload.FileKind.VIDEO_FILE;
import static org.springframework.util.StringUtils.trimLeadingCharacter;
import static org.springframework.util.StringUtils.trimTrailingCharacter;

/**
 * 图片上传
 *
 * @author ZH
 * @date 10:24 2021/12/8
 */
@Slf4j
public class FileUploadClient implements DisposableBean {
    private final MinioUploadProperties minioUploadProperties;
    private final MinioClient minioClient;

    public FileUploadClient(MinioClient minioClient, MinioUploadProperties minioUploadProperties) {
        log.info("[FileUploadClient] constructor ...");
        this.minioClient = minioClient;
        this.minioUploadProperties = minioUploadProperties;
    }

    @Override
    public void destroy() throws Exception {
        minioClient.close();
    }

    /**
     * 上传文件
     *
     * @return 文件uri
     */
    public FileUploadResult upload(MultipartFile file) {
        String filename = file.getOriginalFilename();
        String contentType = file.getContentType();

        try {
            return upload(file.getInputStream(), file.getSize(), filename, contentType);
        } catch (IOException ex) {
            log.error("文件上传时发生异常, msg = {}", ex.getMessage());
            throw new RuntimeException(ex.getMessage());
        }
    }

    /**
     * 上传文件
     *
     * @return 文件uri
     */
    public FileUploadResult upload(File file) throws IOException {
        String contentType = Files.probeContentType(file.toPath());
        return upload(new FileInputStream(file), file.length(), file.getName(), contentType);
    }

    public FileUploadResult upload(byte[] bytes, String filename, String contentType) {
        return upload(new ByteArrayInputStream(bytes), bytes.length, filename, contentType);
    }

    private FileUploadResult upload(InputStream inputStream, long size, String filename, String contentType) {
        FileKind fileKind = lookFileKind(contentType);

        try {
            PutObjectArgs putArgs = PutObjectArgs.builder()
                    .contentType(contentType)
                    .stream(inputStream, size, -1)
                    .object(filename)
                    .bucket(minioUploadProperties.getBucket(fileKind)).build();

            GetPresignedObjectUrlArgs urlArgs = GetPresignedObjectUrlArgs.builder()
                    .object(filename)
                    .bucket(minioUploadProperties.getBucket(fileKind))
//                .expiry() //链接的默认有效期是7day
                    .method(Method.GET).build();

            ObjectWriteResponse response = minioClient.putObject(putArgs);

            String objectUrl = minioClient.getPresignedObjectUrl(urlArgs);

            String etag = trimLeadingCharacter(trimTrailingCharacter(response.etag(), '\"'), '\"');

            return FileUploadResult.ofSuccess(response.object(), etag, response.versionId())
                    .linkUrl(objectUrl);
        } catch (MinioException | GeneralSecurityException ex) {
            log.error("文件服务器minio上传文件失败, msg = {}", ex.getMessage());
            return FileUploadResult.ofFail(filename, ex.getMessage());
        } catch (Exception ex) {
            log.error("文件上传出现错误，cause：{}", ex.getMessage());
            return FileUploadResult.ofFail(filename, ex.getMessage());
        }
    }

    /**
     * 下载常规文件
     *
     * @param filename 文件名
     * @param etag     文件标识
     * @param output   提供文件输出流，function参数为文件的size和contentType
     */
    public void download(String filename, String etag, BiFunction<Long, String, OutputStream> output) {
        download(REGULAR_FILE, filename, etag, output);
    }

    /**
     * 下载指定类型的文件
     *
     * @param fileKind 文件分类 {@link FileKind}
     * @param filename 文件名
     * @param etag     文件标识
     * @param output   提供文件输出流，function参数为文件的size和contentType
     */
    public void download(FileKind fileKind, String filename, String etag, BiFunction<Long, String, OutputStream> output) {
        Objects.requireNonNull(output, "require supplier output");

        StatObjectArgs statArgs = StatObjectArgs.builder()
                .bucket(minioUploadProperties.getBucket(fileKind))
                .object(filename)
                .matchETag(etag)
                .build();

        GetObjectArgs getArgs = GetObjectArgs.builder()
                .bucket(minioUploadProperties.getBucket(fileKind))
                .object(filename)
                .matchETag(etag)
                .build();

        try {
            StatObjectResponse statResponse = minioClient.statObject(statArgs);
            String contentType = statResponse.contentType();
            long size = statResponse.size();

            GetObjectResponse response = minioClient.getObject(getArgs);
            response.transferTo(output.apply(size, contentType));
        } catch (Exception ex) {
            log.error("下载文件出现异常：cause = {}", ex.getMessage());
            throw new RuntimeException(ex.getMessage());
        }
    }

    private static FileKind lookFileKind(String contentType) {
        if (contentType.startsWith(IMAGE_FILE.getAlias())) {
            return IMAGE_FILE;
        }
        if (contentType.startsWith(AUDIO_FILE.getAlias())) {
            return AUDIO_FILE;
        }
        if (contentType.startsWith(VIDEO_FILE.getAlias())) {
            return VIDEO_FILE;
        } else {
            return REGULAR_FILE;
        }
    }

}
